import pygad
import math

print(pygad.__version__)

weight_popScore = 0.1  # 受众评分-权重系数
weight_tags = 1.0  # 知识点匹配-权重系数
weight_typeRate = 0.2  # 题目类型匹配-权重系数
weight_difficultyRate = 0.3  # 题目难度-权重系数
max_popScore = 5.0
max_tags_len = 5

# 题目范围（如果有需要，可以转化）
questionRange = {
    1: {
        'questionId': 8,
        'tags': ['迭代', '递归'],
        'questionType': 0,
        'questionDifficulty': 0
    },
    2: {
        'questionId': 5,
        'tags': ['DFS', '数学'],
        'questionType': 1,
        'questionDifficulty': 0
    },
    3: {
        'questionId': 6,
        'tags': ['迭代', 'DFS', '数学'],
        'questionType': 1,
        'questionDifficulty': 0
    },
    4: {
        'questionId': 7,
        'tags': ['迭代', '递归', '数学'],
        'questionType': 1,
        'questionDifficulty': 1
    },
    5: {
        'questionId': 55,
        'tags': ['迭代', '递归'],
        'questionType': 0,
        'questionDifficulty': 1
    },
    6: {
        'questionId': 69,
        'tags': ['数学'],
        'questionType': 1,
        'questionDifficulty': 1
    },
    7: {
        'questionId': 54,
        'tags': ['迭代', '数学'],
        'questionType': 1,
        'questionDifficulty': 1
    },
    8: {
        'questionId': 68,
        'tags': ['迭代', 'DFS', '数学'],
        'questionType': 0,
        'questionDifficulty': 2
    },
    9: {
        'questionId': 139,
        'tags': ['迭代'],
        'questionType': 0,
        'questionDifficulty': 2
    },
    10: {
        'questionId': 150,
        'tags': ['数学'],
        'questionType': 0,
        'questionDifficulty': 2
    }}
popScore = 3.5
questionNum = 5
needTags = ['递归', 'DFS', 'BFS']
typeRate = [0.4, 0.6]
difficultyRate = [0.2, 0.3, 0.5]


def max_fitness_function(solution, solution_idx):
    # 根据solution索引数组获取题目信息数组
    cur = [questionRange[e] for e in solution]

    # 计算题目类型分布并归一化
    type_distribution = calCurTypeRate(cur)

    # 计算题目难度分布并归一化
    difficulty_distribution = calCurDifficultyRate(cur)

    # 计算题目受众分数
    cur_popScore = calCurPopScore(difficulty_distribution)

    # 计算适应度函数值
    fitness = (
            weight_popScore * (abs(popScore - cur_popScore) / popScore) +
            weight_tags * calTagsLoss(cur) +
            weight_typeRate * (abs(typeRate[0] - type_distribution[0]) + abs(typeRate[1] - type_distribution[1])) / 2 +
            weight_difficultyRate * sum([abs(difficultyRate[i] - difficulty_distribution[i]) for i in range(3)]) / 3
    )

    return 1.0 / fitness


def calCurTypeRate(cur):
    type0, type1 = 0, 0
    for e in cur:
        if e['questionType'] == 0:
            type0 += 1
        else:
            type1 += 1
    sum = type0 + type1

    return [1.0 * type0 / sum, 1.0 * type1 / sum]


def calCurDifficultyRate(cur):
    difficulty0, difficulty1, difficulty2 = 0, 0, 0
    for e in cur:
        if e['questionDifficulty'] == 0:
            difficulty0 += 1
        elif e['questionDifficulty'] == 1:
            difficulty1 += 1
        else:
            difficulty2 += 1
    sum = difficulty0 + difficulty1 + difficulty2

    return [1.0 * difficulty0 / sum, 1.0 * difficulty1 / sum, 1.0 * difficulty2 / sum]


def calCurPopScore(difficulty_distribution):
    weight_score = [1, 3, 5]
    total_score = 0
    for e in [0, 1, 2]:
        total_score += weight_score[e] * difficulty_distribution[e]

    return min(max_popScore, total_score)


def calTagsLoss(cur):
    sum = 0
    for e in cur:
        single_loss = len(set(e['tags']).intersection(set(needTags)))
        sum += single_loss

    return 1.0 * sum / len(cur)

if __name__ == '__main__':
    # pyGad参考链接：https://pygad.readthedocs.io/en/latest/pygad.html#pygad-ga-class
    ga_instance = pygad.GA(
        init_range_low=1,  # 初始种群个体的基因最小值
        init_range_high=len(questionRange),  # 初始种群个体的基因最大值
        num_generations=50,  # 代数
        num_parents_mating=20,  # 要选择的父母解决方案数量
        parent_selection_type="sss",  # 选择策略（steady_state_selection：稳态选择：保证种群稳定性）
        crossover_type="single_point",  # 交叉策略（单点交叉：适用于二进制编码和实数编码等多种编码方式）
        fitness_func=max_fitness_function,  # 适应度函数
        sol_per_pop=100,  # 每一个种群中个体数量
        num_genes=questionNum,  # 染色体基因的数量
        gene_type=int,  # 基因值的数据类型
        gene_space=range(1, 11),  # 基因值的取值范围
        allow_duplicate_genes=False,  # 是否允许染色体中存在重复的值
        mutation_type="random",  # 变异的类型：随机突变
        mutation_percent_genes=5,  # 基因变异的百分比概率
        random_mutation_min_val=1,  # 变异个体的基因最小值
        random_mutation_max_val=len(questionRange)  # 变异个体的基因最大值
    )

    ga_instance.run()
    ga_instance.plot_result()

    solution, solution_fitness, solution_idx = ga_instance.best_solution()
    print("Parameters of the best solution : {solution}".format(solution=solution))
    print("Fitness value of the best solution = {solution_fitness}".format(solution_fitness=solution_fitness))
    print("Index of the best solution : {solution_idx}".format(solution_idx=solution_idx))
